import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;


public class Histo {

    /**
     * Constant for return/exit error
     */
    public static final int ERROR_VALUE = -1;

    /**
     * Flag for threshold option
     */
    public static final String THRESHOLD_OPTION_FLAG = "-t";

    /**
     * Flag for phrase length option
     */
    public static final String PHRASE_LENGTH_FLAG = "-n";

    /**
     * Flag for stop file option
     */
    public static final String STOP_FILE_OPTION_FLAG = "-s";

    /**
     * Flag for Break file option
     */
    public static final String BREAK_OPTION_FLAG = "-b";

    /**
     * A max phrase length that is required for this implementation
     * For extensibility, exceeding the phrase length is a warning for
     * the object, which the main class or caller can treat as an error
     */
    public static final int HISTO_MAX_PHRASE_LENGTH = 9;
    
    /**
     * A min phrase length, see HISTO_MAX_PHRASE_LENGTH
     */
    public static final int HISTO_MIN_PHRASE_LENGTH = 2;
    
    /**
     * Error when a bad phrase length is specified
     */
    public static final String BAD_PHRASE_LENGTH = "Error, argument for -n" +
            " must be a number greater than or equal to 2 and less than" +
            " equal to 9.";
    
    /**
     * The size of phrases to parse.
     */
    private int phraseSize = 1;
    
    /**
     * Object that maintains a list of stopwords
     */
    private StopWordList stops;
    
    /**
     * Tells if we have a stop file.
     */
    private boolean haveStop = false;
    
    /**
     * File to parse stops from
     */
    private String fileStop;
    
    /**
     * Reflects whatever error occurred if a false is returned
     * by the histo functions.
     */
    private String errorMessage = "";
    
    /**
     * A threshold for how many occurrences of a word we must see before we
     *  count it
     */
    private int thresh;
    
    /**
     * Object that maintains the histogram of words
     */
    private PhraseHistogram pHist = null;
    /**
     * Returns the PhraseHistogram object
     * @return
     */
    public PhraseHistogram getPhraseHistogram()
    {
    	return this.pHist;
    }
    
    /**
     * Initialize histo object, using a file containing stop words
     * @param pSize The size of phrases to find
     * @param stopFile The file containing stops
     * @param threshold The number of instances to see before we print a phrase.
     */
    public Histo(int pSize, String stopFile, int threshold) {
        this.phraseSize = pSize;
        this.thresh = threshold;
        if(stopFile!=null)
        {
        	this.fileStop = stopFile;
        	haveStop = true;
        }
    }
    
    /**
     * Initialize histo object without using stop words
     * @param pSize The size of phrases to find
     * @param threshold The number of instances to see before we print a phrase.
     */
    public Histo(int pSize, int threshold) {
        this.phraseSize = pSize;
        this.thresh = threshold;
    }
    

    /**
     * Builds a histogram from a file specified by fileName
     * @param fileName name of the file to read
     * @param overriddenBreaks The overridden variable breaks
     * @return true if we successfully build the histogram
     */
    public boolean buildHisto(String fileName, String overriddenBreaks) throws AIMException{
        FileInputStream fileInput;
        boolean ret = false;
        try {
            /* Open file */
            fileInput = new FileInputStream(fileName);
            
            /* Delegate to other version to perform Histogram */
            ret = this.buildHisto(new BufferedInputStream(fileInput),
                    overriddenBreaks,fileName);
            fileInput.close();
        } catch (IOException e) {
            /*
            * Could not open file We only check for a plain Exception because
            * the function we delegate to handles read errors
            */
        	throw new AIMException(AIMException.FILE_NOT_FOUND);
        }
        return ret;
    }
    
    /**
     * Builds the histogram from an InputStream
     * @param stream The stream to read from
     * @param overriddenBreaks The overridden variable breaks
     * @return True if successful
     */
    public boolean buildHisto(InputStream stream, String overriddenBreaks, String filename) throws AIMException{
        WordTokenizer tok = new WordTokenizer(stream, overriddenBreaks);
        PhraseBuffer pBuffer = new PhraseBuffer(this.phraseSize);
        this.pHist = new PhraseHistogram();
        this.pHist.filename=filename;
        if (!tok.breaksAreValid()) {
            this.errorMessage = tok.getErrorMessage();
            return false;
        }
        this.stops = new StopWordList(overriddenBreaks);
        if (haveStop) {
            try {
                if (!stops.parseFile(fileStop)) {
                    this.errorMessage = stops.getErrorMessage();
                    return false;
                }
            } catch (QUIRKException e) {
            	throw new AIMException(AIMException.CANNOT_READ_STOPWORD_FILE);
            }
        }
        String nextWord;
        try {
            while ((nextWord = tok.nextToken()) != null) {
                nextWord = nextWord.toLowerCase();
                if (!stops.isStop(nextWord)) { // skip stop words
                	pBuffer.flushLineNumbers();
                	pBuffer.addLineNumber(tok.getCurrLineNumber());
                    pBuffer.pushWord(nextWord);
                    if (pBuffer.isFullPhrase()) 
                    {
                        this.pHist.checkPhraseAndUpdate(pBuffer);
                    }
                }
            }
            pHist.numChars=tok.getNumChars();
            pHist.numLines=tok.getNumLines();
            pHist.numWords=tok.getNumWords();
        } catch (IOException e) {
            this.errorMessage = "Failed to read from input file.";
            return false;
        }
        return true;
    }
    
    /**
     * Prints out the histogram generated by the buildHisto functions
     * Histogram must be built using those functions first.
     *
     */
    public void printHisto() {
        if (this.pHist == null) {
            return;
        }
        this.pHist.printHisto(this.thresh);
    }
    
    
    /**
     * @return Returns the errorMessage.
     */
    public String getErrorMessage() {
        return errorMessage;
    }
    
    /**
     * Tests if a phrase length is valid, within min and max phrase
     * lengths, inclusive
     * @param pLength The phrase Length
     * @return true if phraselength is valid
     */
    public static boolean isValidPhraseLength(int pLength) {
        return (pLength >= Histo.HISTO_MIN_PHRASE_LENGTH)
            && (pLength <= Histo.HISTO_MAX_PHRASE_LENGTH);
    }
    
    /**
     * Prints error message when a duplicate option has been specified
     * and terminates application with code of -1
     * @param string The option flag that was duplicated
     */
    private static void duplicateOptionError(String string) {
        System.err.println("Error, the " + string + " option can only be" +
                " specified once.");
        System.exit(ERROR_VALUE);
    }
    
    
    /**
     * Main function
     * @param args
     */
    public static void main(String[] args) throws AIMException{
        int argPos = 0;
        int threshold = 1;
        int phraseLength = 1;
        /* Keep track of options already specified */
        boolean haveStops = false;
        boolean haveTOption = false;
        boolean haveBOption = false;
        boolean haveNOption = false;
        String stopFile = null;
        String overrideBreaks = null;
        String fileName = "";
        boolean haveFile = false;
        /* Parse args, in pairs */
        while ((args.length - argPos) > 1) {
            String flag = args[argPos];
            String option = args[argPos + 1];
            if (flag.equals(BREAK_OPTION_FLAG)) {
                if (haveBOption) {
                    Histo.duplicateOptionError(BREAK_OPTION_FLAG);
                }
                haveBOption = true;
                overrideBreaks = option;
            } else if (flag.equals(STOP_FILE_OPTION_FLAG)) {
                if (haveStops) {
                    Histo.duplicateOptionError(STOP_FILE_OPTION_FLAG);
                }
                stopFile = option;
                haveStops = true;
            } else if (flag.equals(THRESHOLD_OPTION_FLAG)) {
                if (haveTOption) {
                    Histo.duplicateOptionError(THRESHOLD_OPTION_FLAG);
                }
                haveTOption = true;
                try {
                    if ((threshold = Integer.parseInt(option)) < 1) {
                        System.err.println("Error, argument for -t must be a" +
                        " number greater than 0.");
                        System.exit(ERROR_VALUE);
                    }
                } catch (NumberFormatException e) {
                    System.err.println("Error, argument for -t must be a" +
                            " number greater than 0.");
                    System.exit(ERROR_VALUE);
                }
            } else if (flag.equals(PHRASE_LENGTH_FLAG)) {
                if (haveNOption) {
                    Histo.duplicateOptionError(PHRASE_LENGTH_FLAG);
                }
                haveNOption = true;
                try {
                    if ((phraseLength = Integer.parseInt(option)) < 1) {
                        System.err.println(Histo.BAD_PHRASE_LENGTH);
                        System.exit(ERROR_VALUE);
                    }
                    if (!Histo.isValidPhraseLength(phraseLength)) {
                        System.err.println(Histo.BAD_PHRASE_LENGTH);
                        System.exit(ERROR_VALUE);
                    }
                } catch (NumberFormatException e) {
                    System.err.println(Histo.BAD_PHRASE_LENGTH);
                    System.exit(ERROR_VALUE);
                }
            } else {
                System.err.println("Error, unknown option: " + flag);
                System.exit(ERROR_VALUE);
            }
            argPos += 2;
        }
        /* If we have an odd argument left, that is the file name */
        if ((args.length % 2) == 1) {
            fileName = args[args.length - 1];
            haveFile = true;
        }
        Histo hist;
        if (haveStops) {
            hist = new Histo(phraseLength, stopFile, threshold);
        } else {
            hist = new Histo(phraseLength, threshold);
        }
        boolean success = false;
        if (haveFile) {
            success = hist.buildHisto(fileName, overrideBreaks);
        } else {
            success = hist.buildHisto(System.in, overrideBreaks,null);
        }
        if (!success) {
            System.err.println("The following error has occurred: "
                    + hist.getErrorMessage());
            System.exit(ERROR_VALUE);
        } else {
            hist.printHisto();
        }
    }

}
